package com.ponhu.ea2.stockcenter.provider.support.validation;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.ponhu.ea2.stockcenter.provider.support.exception.StockRuntimeException;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.groups.Default;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static com.ponhu.ea2.stockcenter.enums.ResponseCodeEnum.PARAMS_INCORRECT_EXCEPTION_900003;

/**
 * 手动参数校验框架工具类
 * 备注：适用于dubbo service接口参数的校验
 * @author Wang
 * @date 2021/11/10 3:05 下午
 */
public class ValidateUtils {
    private ValidateUtils() {
    }

    // 线程安全的，直接构建也可以，这里使用静态代码块一样的效果
    private static final Validator validator;

    static {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        validator = factory.getValidator();
    }

    /**
     * 快速校验，当出现一个错误的时候直接返回
     * 备注：只能校验对象，不能校验单个参数
     * @param objects
     * @param <T>
     */
    public static <T> void validateFirst(T... objects) {
        validate(true, objects);
    }

    /**
     * 校验所有参数，全部校验完后返回所有错误结果
     * 备注：只能校验对象，不能校验单个参数
     * @param objects
     * @param <T>
     */
    public static <T> void validateAll(T... objects) {
        validate(false, objects);
    }

    /**
     * 校验多个对象
     * @param failFast
     * @param objects
     * @param <T>
     */
    private static <T> void validate(boolean failFast, T... objects) {
        if (objects != null && objects.length > 0) {
            Arrays.stream(objects).forEach(o -> validate(o, failFast));
        } else {
            throw new StockRuntimeException(PARAMS_INCORRECT_EXCEPTION_900003.getCode(), "参数对象不能为空");
        }
    }

    /**
     * 校验单个对象
     *
     * @param t
     * @param <T>
     */
    private static <T> void validate(T t, boolean failFast) {
        if (t == null) {
            throw new StockRuntimeException(PARAMS_INCORRECT_EXCEPTION_900003.getCode(), "参数对象不能为空");
        }
        Set<ConstraintViolation<T>> constraintViolations = validator.validate(t);
        if (constraintViolations.size() > 0) {
            StringBuilder validateError = new StringBuilder();
            for (ConstraintViolation<T> constraintViolation : constraintViolations) {
                validateError.append(constraintViolation.getMessage());
                //如果快速校验，直接返回第一个错误结果
                if (failFast) break;
                validateError.append(";");
            }

            throw new StockRuntimeException(PARAMS_INCORRECT_EXCEPTION_900003.getCode(), validateError.toString());
        }
    }

    // 自定义校验结果返回

    /**
     * 校验实体，返回实体所有属性的校验结果
     *
     * @param obj
     * @param <T>
     * @return
     */
    public static <T> ValidationResult getValidateResult(T obj) {
        // 解析校验结果
        Set<ConstraintViolation<T>> validateSet = validator.validate(obj, Default.class);
        return buildValidationResult(validateSet);
    }

    /**
     * 校验指定实体的指定属性是否存在异常
     *
     * @param obj
     * @param propertyName
     * @param <T>
     * @return
     */
    public static <T> ValidationResult getValidateResult(T obj, String propertyName) {
        Set<ConstraintViolation<T>> validateSet = validator.validateProperty(obj, propertyName, Default.class);
        return buildValidationResult(validateSet);
    }

    /**
     * 将异常结果封装返回
     *
     * @param validateSet
     * @param <T>
     * @return
     */
    private static <T> ValidationResult buildValidationResult(Set<ConstraintViolation<T>> validateSet) {
        ValidationResult validationResult = new ValidationResult();
        if (!CollectionUtils.isEmpty(validateSet)) {
            validationResult.setHasErrors(true);

            List<ValidationErrorNode> errorNodeList = validateSet.stream().map(i -> {
                ValidationErrorNode errorNode = new ValidationErrorNode();
                errorNode.setParameterName(i.getPropertyPath().toString())
                        .setParameterMsg(i.getMessage());
                return errorNode;
            }).collect(Collectors.toList());

            validationResult.setErrorMsgList(errorNodeList);
        }
        return validationResult;
    }
}
